import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from logger import get_module_logger
logger = get_module_logger('fife')

from flask import Blueprint,  request, jsonify,render_template,session
from decorator import login_required
from bt import Bt_api
import json
import re
from datetime import datetime
from werkzeug.utils import secure_filename
from flask import current_app
import shutil



blueprint = Blueprint('/fife', __name__, url_prefix='/fife')

# 获取文件列表
# 文件说明
# 'status':0为文件夹
# 'status':1为文件
path_list = []  # 重命名避免与内置list函数冲突

@blueprint.route("/path", endpoint='index',methods=['GET', 'POST'])
@login_required
# 查看文件列表
def index():
    path = request.args.get('fife_name', '')
    
    if request.method == 'POST':
        # 移除缓存相关代码，直接从宝塔API获取数据
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        fifes=bt_api.GetDir(session.get('bt_fife')+path)
        
        fife_data = []
        # 文件夹数据
        for item in fifes['DIR']:
            row_data = item.split(';')
            time = datetime.fromtimestamp(int(row_data[2]))
            fife_data.append({
                'fife_name': row_data[0],
                # 如果status为0，则表示是文件夹
                'status':0,
                
                'modification_time': time.strftime('%Y-%m-%d %H:%M:%S'),
              
                
            })
         # 文件数据
        for item in fifes['FILES']:
            row_data = item.split(';')

            time = datetime.fromtimestamp(int(row_data[2]))
            if row_data[0]=='.user.ini' or row_data[0]=='docker-compose.yml':
                pass
            else:
                fife_data.append({
                    'fife_name': row_data[0],
                    'file_size': row_data[1],
                    'status':1,
                    'modification_time': time.strftime('%Y-%m-%d %H:%M:%S'),
        
            })

        merge_json={
            "rows":fife_data
        }
        
        # 移除缓存存储代码
        return jsonify(merge_json)
    
    # GET 请求处理，渲染页面
    if path == '':
        path_list.clear()
        path = ''
    elif path == path:
        pass
    x = path.split('/')
   
    path_list.append(path)
    lists = zip(path_list, x)
    cdn=current_app.config['CDN']
    return render_template('fife.html',path=path, url=lists,cdn=cdn)

# 获取文件内容
@blueprint.route("/GetFileBody", endpoint='GetFileBody', methods=['GET', 'POST'])
@login_required
def GetFileBody():
    if request.method == 'POST':
        path = request.form.get('path')
        
        # 移除缓存相关代码，直接从宝塔API获取数据
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.GetFileBody(session.get('bt_fife')+path)
        
        # 移除缓存存储代码
        return jsonify(data)

# 定义保存文件路由
@blueprint.route("/SaveFileBody", endpoint='SaveFileBody', methods=['GET', 'POST'])
@login_required
def SaveFileBody():
    if request.method == 'POST':
        code = request.form.get('code')
        path = request.form.get('path')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.SaveFileBody(code, session.get('bt_fife')+path)
        return jsonify(data)

# 新建文件夹
@blueprint.route("/CreateDir", endpoint='CreateDir', methods=['GET', 'POST'])
@login_required
def CreateDir():
    if request.method == 'POST':
        path = request.form.get('path')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.CreateDir(session.get('bt_fife')+path)
        return jsonify(data)

# 删除文件
@blueprint.route("/DeleteFile", endpoint='DeleteFile', methods=['GET', 'POST'])
@login_required
def DeleteFile():
    if request.method == 'POST':
        path = request.form.get('path')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.DeleteFile(session.get('bt_fife')+path)
        return jsonify(data)
    
# 批量删除文件
@blueprint.route("/SetBatchData", endpoint='SetBatchData', methods=['GET', 'POST'])
@login_required
def SetBatchData():
    if request.method == 'POST':
        path = request.form.get('path')
        ids = request.form.get('ids')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        
        data = bt_api.SetBatchData(str(ids),session.get('id'),session.get('bt_fife')+path)
        return jsonify(data)

# 新建文件
@blueprint.route("/CreateFile", endpoint='CreateFile', methods=['GET', 'POST'])
@login_required
def CreateFile():
    if request.method == 'POST':
        path = request.form.get('path')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.CreateFile(session.get('bt_fife')+path)
        return jsonify(data)

# 批量移动文件
@blueprint.route("/MvFile", endpoint='MvFile', methods=['GET', 'POST'])
@login_required
def MvFile():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        datafile = request.form.get('data')
        # 使用 json.loads()函数将字符串转换为 JSON 对象
        json_data = json.loads(datafile)

        # 移除缓存删除代码，因为我们不再使用缓存
            
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        
        # 遍历所有要移动的文件
        for i in json_data:
            data = bt_api.MvFile(session.get('bt_fife')+sfile+'/'+i['fife_name'],session.get('bt_fife')+dfile+'/'+i['fife_name'])
        
        data2={'msg':'剪切成功'}
        return jsonify(data2)

# 下载文件
@blueprint.route("/DownloadFile", endpoint='DownloadFile', methods=['GET', 'POST'])
@login_required
def DownloadFile():
    if request.method == 'POST':
        path = request.form.get('path')
        logger.info(f"DownloadFile route called with path: {path}")
        logger.debug(f"Session bt_fife: {session.get('bt_fife')}")
        logger.debug(f"Session bt_url: {session.get('bt_url')}")
        
        # 安全检查：防止目录遍历攻击
        import os
        
        # 检查路径是否为空
        if not path:
            return jsonify({
                'status': False,
                'msg': '文件路径不能为空'
            })
        
        # 规范化路径
        try:
            normalized_path = os.path.normpath(path)
        except Exception as e:
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径是否包含危险字符
        if '..' in normalized_path:
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径是否以斜杠开头（相对路径）
        # 但允许以网站根目录开头的路径（如/www/wwwroot/）
        if normalized_path.startswith('/') and not normalized_path.startswith('/www/wwwroot/'):
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径长度
        if len(normalized_path) > 1000:
            return jsonify({
                'status': False,
                'msg': '文件路径过长'
            })
        
        # 检查路径中是否包含特殊字符
        import re
        if re.search(r'[<>"|?*]', normalized_path):
            return jsonify({
                'status': False,
                'msg': '文件路径包含非法字符'
            })
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        
        try:
            # 获取宝塔的下载URL，将相对路径与网站根目录拼接
            # 确保路径拼接正确，避免双斜杠问题
            bt_fife = session.get('bt_fife', '')
            # 移除bt_fife末尾的斜杠（如果存在）
            if bt_fife.endswith('/'):
                bt_fife = bt_fife[:-1]
            # 确保normalized_path不以斜杠开头，或者bt_fife以斜杠结尾
            if normalized_path.startswith('/'):
                full_path = bt_fife + normalized_path
            else:
                full_path = bt_fife + '/' + normalized_path
            logger.debug(f"Full path for download: {full_path}")
            data = bt_api.DownloadFile(full_path)
            
            # 直接返回bt.py处理的结果
            if data.get('status', False):
                return jsonify({
                    'status': True,
                    'download_url': data.get('download_url'),
                    'filename': data.get('filename'),
                    'url_id': data.get('url_id')  # 外链ID，前端可以用来清理
                })
            else:
                # 特殊处理：宝塔API有时会返回status=false但msg中包含下载信息的情况
                # 这种情况下我们仍然可以构造下载链接
                download_info = data.get('msg', {})
                if isinstance(download_info, dict) and 'token' in download_info:
                    # 构造下载URL
                    download_url = f"{session.get('bt_url')}/down/{download_info['token']}"
                    filename = os.path.basename(download_info.get('filename', normalized_path))
                    url_id = download_info.get('id', '')
                    
                    return jsonify({
                        'status': True,
                        'download_url': download_url,
                        'filename': filename,
                        'url_id': url_id
                    })
                
                # 如果获取下载URL失败，返回错误信息
                return jsonify({
                    'status': False,
                    'msg': data.get('msg', '获取下载链接失败')
                })
        except Exception as e:
            # 如果出现异常，返回错误信息
            logger.error(f"获取下载链接失败: {str(e)}")
            return jsonify({
                'status': False,
                'msg': f'获取下载链接失败: {str(e)}'
            })

# 图片预览功能
@blueprint.route("/PreviewImage", endpoint='PreviewImage', methods=['GET', 'POST'])
@login_required
def PreviewImage():
    if request.method == 'POST':
        path = request.form.get('path')
        logger.info(f"PreviewImage route called with path: {path}")
        logger.debug(f"Session bt_fife: {session.get('bt_fife')}")
        logger.debug(f"Session bt_url: {session.get('bt_url')}")
        
        # 安全检查：防止目录遍历攻击
        import os
        
        # 检查路径是否为空
        if not path:
            return jsonify({
                'status': False,
                'msg': '文件路径不能为空'
            })
        
        # 规范化路径
        try:
            normalized_path = os.path.normpath(path)
        except Exception as e:
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径是否包含危险字符
        if '..' in normalized_path:
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径是否以斜杠开头（相对路径）
        # 但允许以网站根目录开头的路径（如/www/wwwroot/）
        if normalized_path.startswith('/') and not normalized_path.startswith('/www/wwwroot/'):
            return jsonify({
                'status': False,
                'msg': '文件路径不合法'
            })
        
        # 检查路径长度
        if len(normalized_path) > 1000:
            return jsonify({
                'status': False,
                'msg': '文件路径过长'
            })
        
        # 检查路径中是否包含特殊字符
        import re
        if re.search(r'[<>"|?*]', normalized_path):
            return jsonify({
                'status': False,
                'msg': '文件路径包含非法字符'
            })
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        
        try:
            # 获取宝塔的预览URL，将相对路径与网站根目录拼接
            # 确保路径拼接正确，避免双斜杠问题
            bt_fife = session.get('bt_fife', '')
            # 移除bt_fife末尾的斜杠（如果存在）
            if bt_fife.endswith('/'):
                bt_fife = bt_fife[:-1]
            # 确保normalized_path不以斜杠开头，或者bt_fife以斜杠结尾
            if normalized_path.startswith('/'):
                full_path = bt_fife + normalized_path
            else:
                full_path = bt_fife + '/' + normalized_path
            logger.debug(f"Full path for preview: {full_path}")
            data = bt_api.DownloadFile(full_path)
            
            # 直接返回bt.py处理的结果
            if data.get('status', False):
                return jsonify({
                    'status': True,
                    'preview_url': data.get('download_url'),
                    'filename': data.get('filename'),
                    'url_id': data.get('url_id')  # 外链ID，前端可以用来清理
                })
            else:
                # 特殊处理：宝塔API有时会返回status=false但msg中包含下载信息的情况
                # 这种情况下我们仍然可以构造预览链接
                download_info = data.get('msg', {})
                if isinstance(download_info, dict) and 'token' in download_info:
                    # 构造预览URL
                    preview_url = f"{session.get('bt_url')}/down/{download_info['token']}"
                    filename = os.path.basename(download_info.get('filename', normalized_path))
                    url_id = download_info.get('id', '')
                    
                    return jsonify({
                        'status': True,
                        'preview_url': preview_url,
                        'filename': filename,
                        'url_id': url_id
                    })
                
                # 如果获取预览URL失败，返回错误信息
                return jsonify({
                    'status': False,
                    'msg': data.get('msg', '获取预览链接失败')
                })
        except Exception as e:
            # 如果出现异常，返回错误信息
            logger.error(f"获取预览链接失败: {str(e)}")
            return jsonify({
                'status': False,
                'msg': f'获取预览链接失败: {str(e)}'
            })

# 移动单个文件
@blueprint.route("/MvFile2", endpoint='MvFile2', methods=['GET', 'POST'])
@login_required
def MvFile2():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.MvFile(session.get('bt_fife')+sfile,session.get('bt_fife')+dfile)
        data2={'msg':'剪切成功'}
        return jsonify(data2)

# 移动单个文件
@blueprint.route("/MvFile3", endpoint='MvFile3', methods=['GET', 'POST'])
@login_required
def MvFile3():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.MvFile(session.get('bt_fife')+sfile,session.get('bt_fife')+dfile)
        
        return jsonify(data)
    
# 复制文件
@blueprint.route("/CopyFile", endpoint='CopyFile', methods=['GET', 'POST'])
@login_required
def CopyFile():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        
        # 移除缓存删除代码，因为我们不再使用缓存
        
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.CopyFile(session.get('bt_fife')+sfile,session.get('bt_fife')+dfile)
        return jsonify(data)
    

# 压缩文件
@blueprint.route("/Zip", endpoint='Zip', methods=['GET', 'POST'])
@login_required
def Zip():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        z_type= request.form.get('z_type')
        path= request.form.get('path')
        
       
        
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.Zip(sfile,session.get('bt_fife')+dfile,z_type,session.get('bt_fife')+path)
        return jsonify(data)
    
# 解压文件
@blueprint.route("/unzip", endpoint='unzip', methods=['GET', 'POST'])
@login_required
def unzip():
    if request.method == 'POST':
        sfile = request.form.get('sfile')
        dfile= request.form.get('dfile')
        
        
        
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.UnZip(session.get('bt_fife')+dfile+'/'+sfile,session.get('bt_fife')+dfile)
        return jsonify(data)
    
# 获取日志
@blueprint.route("/GetSiteLogs", endpoint='GetSiteLogs', methods=['GET', 'POST'])
@login_required
def GetLog():
    # if request.method == 'POST':
        # path = request.form.get('path')
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.GetSiteLogs('aa.com')
       
     
        # 定义正则表达式模式
        pattern =  r'200\s+(\d+)'

        # 匹配字符串
        matches = re.findall(pattern, data['msg'])
        total = sum(int(n) for n in matches)

       
        return jsonify(str(total)+'b')


# 上传
@blueprint.route("/upload", endpoint='upload', methods=['GET', 'POST'])
@login_required
def upload():
    path = request.args.get('path') 
    cdn=current_app.config['CDN']     
    return render_template('fife.html',path=path,cdn=cdn)

# f.save(os.path.join(session.get('bt_fife')+path2, f.filename))  
# return jsonify({'code':200,'msg':path2})
def allowed_file(filename):
    # 始终返回True，表示接受所有文件类型
    return True
# 上传
def encode_filename(filename):
    """将文件名转换为 URL 安全的格式。"""
    return filename



@blueprint.route("/upload_api", endpoint='upload_api', methods=['GET', 'POST'])
@login_required
def upload_api(): 
    try:
        logger.debug(f"\n=== 文件上传调试 ===")
        logger.debug(f"Request method: {request.method}")
        logger.debug(f"Request files: {list(request.files.keys())}")
        logger.debug(f"Request form keys: {list(request.form.keys())}")
        
        # 检查文件
        if 'file' not in request.files:
            logger.warning("错误: 未找到上传文件")
            return jsonify({'code': 400, 'msg': '未找到上传文件'}), 400
            
        file = request.files['file']
        if file.filename == '':
            logger.warning("错误: 文件内容为空")
            return jsonify({'code': 400, 'msg': '文件内容为空'}), 400
            
        # 获取上传路径和其他参数
        path = request.form.get('path', '') or ''
        upload_dir = session.get('bt_fife') or ''
        target_path = upload_dir + path
        
        # 检查是否是分片上传
        chunk_index = request.form.get('chunkIndex')
        file_name = request.form.get('fileName')
        total_chunks = request.form.get('totalChunks')
        
        logger.debug(f"Upload path: {target_path}")
        logger.debug(f"File name: {file.filename}")
        logger.debug(f"Chunk index: {chunk_index}")
        logger.debug(f"File name: {file_name}")
        logger.debug(f"Total chunks: {total_chunks}")
        
        # 如果是分片上传
        if chunk_index is not None and file_name is not None:
            chunk_index = int(chunk_index)
            total_chunks = int(total_chunks) if total_chunks else 1
            
            # 直接将分片文件保存到目标目录
            chunk_file_path = os.path.join(target_path, f"{file_name}.part{chunk_index}")
            # 确保目录存在
            os.makedirs(target_path, exist_ok=True)
            file.save(chunk_file_path)
            logger.debug(f"分片文件保存路径: {chunk_file_path}")
            
            # 检查是否是最后一个分片
            if chunk_index == total_chunks - 1:
                # 合并所有分片
                try:
                    merge_result = merge_chunks_internal(file_name, path, upload_dir, total_chunks)
                    if merge_result['success']:
                        # 上传合并后的文件到宝塔
                        final_file_path = merge_result['finalPath']
                        bt_api = Bt_api(session.get('bt_url'), session.get('bt_key'))
                        upload_result = bt_api.UploadFileDirect(final_file_path, target_path)
                        logger.debug(f"宝塔API上传结果: {upload_result}")
                        
                        # 删除合并后的文件
                        if os.path.exists(final_file_path):
                            os.remove(final_file_path)
                        
                        # 移除缓存删除代码，因为我们不再使用缓存
                        
                        # 返回完成状态
                        return jsonify({
                            'code': 200, 
                            'msg': '文件上传成功',
                            'data': {
                                'fileName': file_name,
                                'isComplete': True
                            }
                        })
                    else:
                        return jsonify({
                            'code': 500, 
                            'msg': f'文件合并失败: {merge_result["error"]}'
                        }), 500
                except Exception as e:
                    logger.error(f"文件合并失败: {str(e)}")
                    return jsonify({
                        'code': 500, 
                        'msg': f'文件合并失败: {str(e)}'
                    }), 500
            else:
                # 返回分片上传成功状态
                return jsonify({
                    'code': 200, 
                    'msg': '分片上传成功',
                    'data': {
                        'chunkIndex': chunk_index,
                        'fileName': file_name,
                        'isComplete': False
                    }
                })
        else:
            # 直接上传整个文件
            # 保存临时文件
            import tempfile
            temp_dir = tempfile.gettempdir()
            temp_file_path = os.path.join(temp_dir, file.filename)
            file.save(temp_file_path)
            
            # 使用宝塔API直接上传文件
            bt_api = Bt_api(session.get('bt_url'), session.get('bt_key'))
            upload_result = bt_api.UploadFileDirect(temp_file_path, target_path)
            logger.debug(f"宝塔API上传结果: {upload_result}")
            
            # 删除临时文件
            if os.path.exists(temp_file_path):
                os.remove(temp_file_path)
            
            # 移除缓存删除代码，因为我们不再使用缓存
            
            # 检查上传结果
            if upload_result.get('status', False):
                return jsonify({
                    'code': 200, 
                    'msg': '文件上传成功',
                    'data': {
                        'msg': upload_result.get('msg', '上传成功'),
                        'status': upload_result.get('status', True),
                        'isComplete': True
                    }
                })
            else:
                error_msg = upload_result.get("msg", "未知错误")
                logger.error(f"上传失败: {error_msg}")
                return jsonify({
                    'code': 500, 
                    'msg': f'文件上传失败: {error_msg}'
                }), 500
        
    except Exception as e:
        logger.error(f"文件上传失败 - 错误: {str(e)}")
        import traceback
        logger.error(f"文件上传失败 - 详细错误: {traceback.format_exc()}")
        return jsonify({'code': 500, 'msg': f'文件上传失败: {str(e)}'}), 500

# 内部合并函数
def merge_chunks_internal(file_name, path, upload_dir, total_chunks):
    try:
        upload_path = upload_dir + path
        final_file_path = os.path.join(upload_path, file_name)
        
        # 确保目录存在
        os.makedirs(upload_path, exist_ok=True)
        
        # 检查所有分片是否存在
        chunk_files = []
        for i in range(total_chunks):
            chunk_path = os.path.join(upload_path, f"{file_name}.part{i}")
            if not os.path.exists(chunk_path):
                return {'success': False, 'error': f'分片 {i} 不存在'}
            chunk_files.append(chunk_path)
        
        # 合并文件
        with open(final_file_path, 'wb') as final_file:
            for chunk_path in chunk_files:
                with open(chunk_path, 'rb') as chunk_file:
                    shutil.copyfileobj(chunk_file, final_file)
                # 删除分片文件
                os.remove(chunk_path)
                
        logger.info(f"文件合并成功: {final_file_path}")
        return {'success': True, 'finalPath': final_file_path}
        
    except Exception as e:
        logger.error(f"合并错误: {str(e)}")
        return {'success': False, 'error': str(e)}

# 计算文件夹大小
@blueprint.route("/get_path_size", endpoint='get_path_size', methods=['GET', 'POST'])
@login_required
def get_path_size():
    if request.method == 'POST':
        path = request.form.get('path')
        
        # 实例化宝塔API对象
        bt_api=Bt_api(session.get('bt_url'),session.get('bt_key'))
        data = bt_api.get_path_size(session.get('bt_fife')+path)
        
        # 添加调试日志
        logger.debug(f"get_path_size response: {data}")
        
        return jsonify(data)


# 清理下载外链
@blueprint.route("/RemoveDownloadUrl", endpoint='RemoveDownloadUrl', methods=['POST'])
@login_required
def RemoveDownloadUrl():
    if request.method == 'POST':
        url_id = request.form.get('url_id')
        
        # 检查url_id是否存在
        if not url_id:
            return jsonify({
                'status': False,
                'msg': '外链ID不能为空'
            })
        
        # 实例化宝塔API对象
        bt_api = Bt_api(session.get('bt_url'), session.get('bt_key'))
        
        try:
            # 调用宝塔API删除下载外链
            result = bt_api.remove_download_url(url_id)
            return jsonify(result)
        except Exception as e:
            # 如果出现异常，返回错误信息
            return jsonify({
                'status': False,
                'msg': f'清理外链失败: {str(e)}'
            })
